package dk.viauc.cmclang.intlang;

import java.util.Vector;

import dk.viauc.cmclang.intlang.ast.ArrayGetExpression;
import dk.viauc.cmclang.intlang.ast.BinaryExpression;
import dk.viauc.cmclang.intlang.ast.Block;
import dk.viauc.cmclang.intlang.ast.BoolLitExpression;
import dk.viauc.cmclang.intlang.ast.BoolLiteral;
import dk.viauc.cmclang.intlang.ast.CallExpression;
import dk.viauc.cmclang.intlang.ast.Declaration;
import dk.viauc.cmclang.intlang.ast.DeclarationList;
import dk.viauc.cmclang.intlang.ast.DecsStatement;
import dk.viauc.cmclang.intlang.ast.ExpList;
import dk.viauc.cmclang.intlang.ast.Expression;
import dk.viauc.cmclang.intlang.ast.ExpressionStatement;
import dk.viauc.cmclang.intlang.ast.FunctionDeclaration;
import dk.viauc.cmclang.intlang.ast.Identifier;
import dk.viauc.cmclang.intlang.ast.IfStatement;
import dk.viauc.cmclang.intlang.ast.IntLitExpression;
import dk.viauc.cmclang.intlang.ast.IntLiteralList;
import dk.viauc.cmclang.intlang.ast.IntegerLiteral;
import dk.viauc.cmclang.intlang.ast.Operator;
import dk.viauc.cmclang.intlang.ast.Program;
import dk.viauc.cmclang.intlang.ast.ReadStatement;
import dk.viauc.cmclang.intlang.ast.ReturnStatement;
import dk.viauc.cmclang.intlang.ast.Statement;
import dk.viauc.cmclang.intlang.ast.StatementList;
import dk.viauc.cmclang.intlang.ast.Statements;
import dk.viauc.cmclang.intlang.ast.Type;
import dk.viauc.cmclang.intlang.ast.VarExpression;
import dk.viauc.cmclang.intlang.ast.VariableDeclaration;
import dk.viauc.cmclang.intlang.ast.Visitor;
import dk.viauc.cmclang.intlang.ast.WhileStatement;
import dk.viauc.cmclang.intlang.ast.WriteStatament;

public class Checker implements Visitor{

	private IdentificationTable idTable = new IdentificationTable();
	
	public void check( Program p )
	{
		p.visit( this, null );
	}
	
	public Object visitProgram(Program program, Object arg) {
		program.block.visit( this, null );
		return null;
	}


	public Object visitBlock(Block block, Object arg) {
		for(FunctionDeclaration dec: block.functions)
			dec.visit(this, null);
		
		return null;
	}


	public Object visitFunctionDeclaration(
			FunctionDeclaration functionDeclaration, Object arg) {
		String id = (String) functionDeclaration.ident.visit(this, null);
		
		idTable.enter( id, functionDeclaration );
		idTable.openScope();
		functionDeclaration.type.visit(this, null);
		if(functionDeclaration.params != null)
			functionDeclaration.params.visit(this,null);
		functionDeclaration.statements.visit(this, null);
		idTable.closeScope();
		return null;
	}

	public Object visitIdentifier(Identifier identifier, Object arg) {
		return identifier.spelling;
	}

	public Object visitType(Type type, Object arg) {
		return type.type;
	}

	public Object visitDeclarationList(DeclarationList declarationList,
			Object arg) {
		for(Declaration oneDec: declarationList.dec)
		{
			oneDec.visit(this, null);
		}
		return null;
	}

	public Object visitVariableDeclaration(
			VariableDeclaration variableDeclaration, Object arg) {
		
		String id = (String)variableDeclaration.id.visit(this, null);
		idTable.enter( id, variableDeclaration );
		
		variableDeclaration.type.visit(this, null);
		if(variableDeclaration.term != null)
			variableDeclaration.term.visit(this, null);
		
		return null;
	}
	
	public Object visitStatementList(StatementList statementList, Object arg) {
		for(Statement st: statementList.statements)
			st.visit(this, null);
		return null;
	}


	public Object visitArrayGetExpression(
			ArrayGetExpression arrayGetExpression, Object arg) {
		arrayGetExpression.id.visit(this, null);
		arrayGetExpression.il.visit(this, null);
		return new CheckerType(false);
	}


	public Object visitBinaryExpression(BinaryExpression binaryExpression,
			Object arg) {
		CheckerType t1 = (CheckerType) binaryExpression.operand1.visit(this, null);
		CheckerType t2 = (CheckerType) binaryExpression.operand2.visit(this, null);	
		String operator = (String) binaryExpression.operator.visit(this, null);
		
		if(operator.equals("BE") && t1.rvalueOnly)
			System.out.println( "Left-hand side of BE must be a variable" );
				
		return new CheckerType(true);
	}
	
	public Object visitOperator(Operator operator, Object arg) {
		return operator.spelling;
	}

	public Object visitExpList(ExpList expList, Object arg) {
		Vector<CheckerType> types = new Vector<CheckerType>();
		
		for( Expression exp: expList.exp )
			types.add( (CheckerType) exp.visit( this, null ) );
			
		return types;
	}
	
	public Object visitIfStatement(IfStatement ifStatement, Object arg) {
		ifStatement.exp.visit(this, null);
		ifStatement.doPart.visit(this, null);
		if(ifStatement.elsePart != null)
			ifStatement.elsePart.visit(this, null);
		
		return null;
	}
	
	public Object visitWhileStatement(WhileStatement whileStatement, Object arg) {
		whileStatement.exp.visit(this,  null);
		whileStatement.stats.visit(this,  null);
		
		return null;
	}
	
	public Object visitWriteStatement(WriteStatament writeStatament, Object arg) {
		writeStatament.exp.visit(this, null);
		
		return null;
	}
	
	public Object visitExpressionStatement(
			ExpressionStatement expressionStatement, Object arg) {
		expressionStatement.exp.visit(this, null);
		
		return null;
	}
	
	public Object visitVarExpression(VarExpression varExpression, Object arg) {
		String id = (String) varExpression.id.visit(this, null);
		
		Declaration d = idTable.retrieve(id);
		if(d == null)
			System.out.println(id + " is not declared");
		else if(!(d instanceof VariableDeclaration))
			System.out.println( id + " is not a variable");
		else
			varExpression.decl = (VariableDeclaration) d;
		
		return new CheckerType(false);
	}
	
	public Object visitIntegerLiteral(IntegerLiteral integerLiteral, Object arg) {
		return null;
	}
	
	public Object visitReadStatement(ReadStatement readStatement, Object arg) {
		readStatement.exp.visit(this, null);
		
		return null;
	}
	
	public Object visitCallExpression(CallExpression callExpression, Object arg) {
		String id = (String) callExpression.name.visit( this, null );
		Vector<CheckerType> t = (Vector<CheckerType>)( callExpression.explist.visit( this, null ) );
		
		Declaration d = idTable.retrieve( id );
		if( d == null )
			System.out.println( id + " is not declared" );
		else if( !( d instanceof FunctionDeclaration ) )
			System.out.println( id + " is not a function" );
		else {
			FunctionDeclaration f = (FunctionDeclaration) d;
			callExpression.decl = f;
			
			if(f.params != null)
				if( f.params.dec.size() != t.size() )
					System.out.println( "Incorrect number of arguments in call to " + id );
		}
		
		return new CheckerType( true );
	}
	
	public Object visitIntLitExpression(IntLitExpression intLitExpression,
			Object arg) {
		intLitExpression.literal.visit(this, null);
		
		return new CheckerType(true);
	}
	
	public Object visitBoolLitExpression(BoolLitExpression boolLitExpression,
			Object arg) {
		boolLitExpression.bool.visit(this, null);
		
		return new CheckerType(true);
	}

	
	public Object visitBoolLiteral(BoolLiteral boolLiteral, Object arg) {
		return null;
	}


	public Object visitDecsStatement(DecsStatement decsStatement, Object arg) {
		decsStatement.stlist.visit(this, null);
		
		return null;
	}


	public Object visitIntLiteralList(IntLiteralList intLiteralList, Object arg) {
		for(IntegerLiteral il : intLiteralList.intlist)
			il.visit(this, null);
		
		return null;
	}

	public Object visitReturnStatement(ReturnStatement returnStatement,
			Object arg) {
		returnStatement.retExpresssion.visit(this, null);
		
		return null;
	}

	public Object visitStatements(Statements statements, Object arg) {
		for(Statement st : statements.stat)
			st.visit(this, null);
		
		return null;
	}

}
